﻿package br.com.ir.tcc.geco.service;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.springframework.stereotype.Component;

import br.com.ir.tcc.geco.modelo.Combinacao;
import br.com.ir.tcc.geco.modelo.CombinacaoTemplate;
import br.com.ir.tcc.geco.modelo.Modelo;

@Component
public class CombinacaoService extends GenericService{
	
	public void saveCombinacao(Combinacao combinacao){
		Object identity = null;
		
		try {
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			// decidir esquema de ordenação
			
		} catch (Exception exception){
			exception.printStackTrace();
		} finally {
			closeSession(identity);
		}
	}
	
	@SuppressWarnings("unchecked")
	public List findCombinacaoTemplate(CombinacaoTemplate combinacaoTemplate){

		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();

			List parameters = new ArrayList();
			
			StringBuilder hql = new StringBuilder();
			hql.append("\n	FROM CombinacaoTemplate ct");
			hql.append("\n	INNER JOIN FETCH ct.template");
			hql.append("\n	INNER JOIN FETCH ct.combinacao");
			hql.append("\n	WHERE 1=1");
			appendFilter(parameters, hql, "\n AND ct.chaveObjetoModelo = ? ", combinacaoTemplate.getChaveObjetoModelo());
			appendFilter(parameters, hql, "\n AND ct.combinacao.id = ? ", combinacaoTemplate.getCombinacao().getId());
			appendFilter(parameters, hql, "\n AND ct.combinacao.modelo.id = ? ", combinacaoTemplate.getCombinacao().getModelo().getId());
			
			Query query = session.createQuery(hql.toString());
			setProperties(query, parameters);
			return query.list();
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	public Combinacao findCombinacao(Integer id){

		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();

			StringBuilder hql = new StringBuilder();
			hql.append("\n	FROM Combinacao c");
			hql.append("\n	INNER JOIN FETCH c.modelo");
			hql.append("\n	WHERE c.id = :id ");
			
			Query query = session.createQuery(hql.toString());
			query.setParameter("id",id);
			
			return (Combinacao) query.uniqueResult();
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	public void saveCombinacaoTemplate(CombinacaoTemplate combinacaoTemplate){
		
		Object identity = null;
		
		Transaction tx = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			tx = session.getTransaction();
			tx.begin();
			
			Object idTpl =  combinacaoTemplate.getTemplate().getId();
			
			if (idTpl == null)
				session.save(combinacaoTemplate.getTemplate());
			 else 
				session.merge(combinacaoTemplate.getTemplate());
			
			Object idCombTpl =  combinacaoTemplate.getId();
			
			if (idCombTpl == null)
				session.save(combinacaoTemplate);
			 else 
				session.merge(combinacaoTemplate);
			
			tx.commit();
			session.flush();
		} catch (Exception e) {
			tx.rollback();
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	@SuppressWarnings("unchecked")
	public String getGeneratedCode(Integer idCombinacao){

		Object identity = null;
		StringWriter writer = new StringWriter();
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			Combinacao combinacao = (Combinacao) session.createQuery(
										"FROM Combinacao c INNER JOIN FETCH c.modelo WHERE c.id = :id")
										.setParameter("id", idCombinacao)
										.uniqueResult();
			
			List<CombinacaoTemplate> templates = session.createQuery(
										"FROM CombinacaoTemplate ct WHERE ct.combinacao.id = :id ORDER BY ct.nivel, ct.ordem")
										.setParameter("id", idCombinacao)
										.list();
			
			Map<String,Object> objectModel = (Map<String,Object>) combinacao.getModelo().getObjectModel();
			Entry<String,Object> objectRoot = (Entry<String,Object>) objectModel.entrySet().iterator().next();
			
			Velocity.init();
			
			for (CombinacaoTemplate combinacaoTemplate : templates){
				
				// contexto para merge com template
				VelocityContext context = new VelocityContext();
				
				// coloca modelo no contexto
				context.put((String) objectRoot.getKey(), objectRoot.getValue());
				
				// publica propriedades do objeto modelo no contexto
				Map<String,Object> objetoCorrente = (Map<String,Object>) 
					PropertyUtils.getProperty((Map)objectModel, combinacaoTemplate.getChaveObjetoModelo());
				
				for(Entry<String,Object> entry : objetoCorrente.entrySet()){
					if(!(entry instanceof Map))
						context.put(entry.getKey(), entry.getValue());
				}
				
				// gera código
				Velocity.evaluate(context, writer, "", 
						combinacaoTemplate.getTemplate().getFonte());
				
				writer.append("\n");
				
			}
			
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
		return writer.toString();
		
	}
	
	
	
	
	public List getAssociacoes(Integer idCombinacao){
		
		Object identity = null;
		StringWriter writer = new StringWriter();
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			return session.createQuery("FROM CombinacaoTemplate ct WHERE ct.combinacao.id = :id ORDER BY ct.nivel, ct.ordem")
										.setParameter("id", idCombinacao)
										.list();
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	
	@SuppressWarnings("unchecked")
	public String getGeneratedCode2(Integer idCombinacao){

		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			Combinacao combinacao = (Combinacao) session.createQuery(
										"FROM Combinacao c INNER JOIN FETCH c.modelo WHERE c.id = :id")
										.setParameter("id", idCombinacao)
										.uniqueResult();
			
			List<CombinacaoTemplate> templates = session.createQuery(
										"FROM CombinacaoTemplate ct WHERE ct.combinacao.id = :id ORDER BY ct.nivel, ct.ordem")
										.setParameter("id", idCombinacao)
										.list();
			
			Map<String, CombinacaoTemplate> associacoes = new HashMap<String, CombinacaoTemplate>();
			
			for (CombinacaoTemplate combinacaoTemplate : templates)
				associacoes.put(combinacaoTemplate.getChaveObjetoModelo(), combinacaoTemplate);
			

			Map<String,Object> objectModel = (Map<String,Object>) combinacao.getModelo().getObjectModel();
			Entry<String,Object> objectRoot = (Entry<String,Object>) objectModel.entrySet().iterator().next();
			
			Velocity.init();
			
			return doSomething(objectModel,objectRoot,associacoes);			
			
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	
	@SuppressWarnings("unchecked")
	private String doSomething(Map<String,Object> map, Entry<String,Object> objectRoot, Map<String, CombinacaoTemplate> associacoes) throws Exception{
		
		// gerar código dos filhos ordenando irmãos
		
		String codeResult = null;
		
		Map<Integer,String> brothers = new HashMap<Integer, String>();
		for (Object object : map.values()) {
			
			if(object instanceof Map){
				Map<String, Object> childMap = (Map<String, Object>) object;
				String code = doSomething(childMap,objectRoot,associacoes);
				String id = (String) childMap.get(Modelo.IDENTIFICACAO_OBJETO_YAML);
				CombinacaoTemplate combinacaoTemplate = associacoes.get(id);
				brothers.put(combinacaoTemplate != null ? combinacaoTemplate.getOrdem(): 0, code);
			}
			
		}
		
		// ordenar 
		
		List<Integer> listaChamada = new ArrayList<Integer>(brothers.keySet());
		Collections.sort(listaChamada);
		StringBuilder childCodeBuilder = new StringBuilder();
		for (Integer ordem : listaChamada) {
			childCodeBuilder.append(brothers.get(ordem));
		}
		
		String childCode = childCodeBuilder.toString();		
		
		// gerar código corrente
		String id = (String) map.get(Modelo.IDENTIFICACAO_OBJETO_YAML);
		CombinacaoTemplate combinacaoTemplate = associacoes.get(id);
		
		if(combinacaoTemplate != null){
			
			/* coloca childCode, modelo, e propriedades do objeto corrente no contexto ***********/
			
			VelocityContext context = new VelocityContext();
			StringWriter writer = new StringWriter();
			
			context.put("childCode", childCode);
			context.put((String) objectRoot.getKey(), objectRoot.getValue());
			
			for(Entry<String,Object> entry : map.entrySet()){
				if(!(entry.getValue() instanceof Map))
					context.put(entry.getKey(), entry.getValue());
			}
			
			String templateFonte = combinacaoTemplate.getTemplate().getFonte();			
			Velocity.evaluate(context, writer, "", templateFonte.contains("$childCode") ? templateFonte : templateFonte + "$childCode"); 
			String currentCode = writer.toString();
			
			// gerar código envolvido
			context = new VelocityContext();
			writer = new StringWriter();
			
			context.put("currentCode", currentCode);
			String fontePrincipal = combinacaoTemplate.getFontePrincipal() == null ? "" : combinacaoTemplate.getFontePrincipal();
			Velocity.evaluate(context, writer, "", fontePrincipal.contains("$currentCode") ? fontePrincipal : fontePrincipal + "$currentCode"); 
			codeResult = writer.toString();
			
		} else {
			codeResult = childCode;
		}
		
		return codeResult + "\n";
		
	}
	
	public void removeCombinacao(Integer idCombinacao) {
		
		Object identity = null;
		Transaction tx = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			tx = session.beginTransaction();
			Combinacao c = (Combinacao) session.get(Combinacao.class, idCombinacao);
			session.delete(c);
			tx.commit();
						
		} catch (Exception e) {
			tx.rollback();
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
	}

	public List findCombinacoes(Integer idModelo) {
		
		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			return session.createQuery("FROM Combinacao c WHERE c.modelo.id = :id ")
									.setParameter("id", idModelo).list();
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
	}
	

}
